#include <assert.h>
#include <string>
#include <vector>
#include <memory>
#include <mgapiall.h>

#include "resource.h"
#include "animation_player.h"
#include "animation_player_controller.h"
#include "animation_player_view.h"

using namespace std;
using namespace std::tr1;
using namespace tl;

namespace
{
inline void SetEnabled (mggui gui, bool value)
{
   mgSetEnabled (gui, value ? MG_TRUE : MG_FALSE);
}

inline animation_player_view& ToAnimationPlayerView (void* userData)
{
   assert(userData);
   animation_player_view* pView = static_cast<animation_player_view*>(userData);
   return *pView;
}

class refresh_dialog_functor : public animation_player::post_update_frame_functor
{
public:
	refresh_dialog_functor (mggui dialog) :
		m_dialog (dialog)
		{
		}

		virtual void operator () (const animation_player*)
		{
			mgRefreshDialog (m_dialog);
		}
private:
	mggui m_dialog;
};

mgstatus StopCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
                       void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_ACTIVATE:
      player.Stop ();
      view.RefreshDialog ();
      break;
   case MGCB_REFRESH:
      SetEnabled (gui, !player.GetAnimationPath ().empty ());
      break;
   }

   return MSTAT_OK;
}

mgstatus PreviousCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
                           void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_ACTIVATE:
      player.GoToPreviousFrame ();
      view.RefreshDialog ();
      break;
   case MGCB_REFRESH:
      SetEnabled (gui, !player.GetAnimationPath ().empty ());
      break;
   }

   return MSTAT_OK;
}

mgstatus PlayCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
   void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_ACTIVATE:
      player.IsPlaying () ? player.Pause () : player.Play ();
      view.RefreshDialog ();
      break;
   case MGCB_REFRESH:
      SetEnabled (gui, !player.GetAnimationPath ().empty ());
      mgTextSetString (gui, player.IsPlaying () ? "Pause" : "Play");
      break;
   }

   return MSTAT_OK;
}

mgstatus NextCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
                       void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_ACTIVATE:
      player.GoToNextFrame ();
      view.RefreshDialog ();
      break;
   case MGCB_REFRESH:
      SetEnabled (gui, !player.GetAnimationPath ().empty ());
      break;
   }

   return MSTAT_OK;
}

mgstatus LoadCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
   void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_ACTIVATE:
      {
         mgstringlist fileList = NULL;
         int numFiles;
         mgstatus status = mgPromptDialogFile (
            view.GetDialog (), MPFM_OPEN, &numFiles, &fileList,
            MPFA_PATTERN, "BVH Files|*.bvh",
            MPFA_TITLE, "Load Animation",
            MPFA_FULLFILENAME, player.GetAnimationPath ().c_str (), NULL);

         if (MSTAT_ISOK(status))
         {
            if (fileList)
            {
               player.LoadBVHFile (*fileList);

               mgFreeStringList (fileList);
               fileList = NULL;
            }
         }

         view.RefreshDialog ();
      }
      break;
   case MGCB_REFRESH:
      SetEnabled (gui, !player.IsPlaying ());
      break;
   }

   return MSTAT_OK;
}

mgstatus SliderCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
                         void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_ACTIVATE:
      {
         double value;
         mgstatus status = mgScaleGetValue (gui, &value);
         if (MSTAT_ISOK(status))
         {
            player.Pause ();
            player.GoToFrame (static_cast<int>(value - 1));
            view.RefreshDialog ();
         }
      }
      break;
   case MGCB_REFRESH:
      {
         const frame_number& frame = player.GetFrame ();
         mgScaleSetMinMax (gui, 1, frame.GetFrameCount ());
         mgScaleSetValue (gui, frame.Get () + 1);
      }
      break;
   }

   return MSTAT_OK;
}

mgstatus FrameTextBoxCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
                               void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_ACTIVATE:
      {
         int value;
         mgstatus status = mgTextGetInteger (gui, &value);
         if (MSTAT_ISOK(status))
         {
            player.GoToFrame (value - 1);
            view.RefreshDialog ();
         }
      }
      break;
   case MGCB_REFRESH:
      {
         int frameNumber = static_cast<int>(player.GetFrame ().Get ()) + 1;
         mgTextSetInteger (gui, frameNumber, NULL);
      }
      break;
   }

   return MSTAT_OK;
}

mgstatus FrameCountCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
                             void* userData, void* callData)
{
   if (callbackReason == MGCB_REFRESH)
   {
      animation_player_view& view = ToAnimationPlayerView (userData);
      animation_player& player = view.GetAnimationPlayer ();
      int frameCount = static_cast<int>(player.GetFrame ().GetFrameCount ());

      mgTextSetInteger (gui, frameCount, NULL);
   }

   return MSTAT_OK;
}

mgstatus MessageCallback (mggui gui, mgcontrolid controlId, mgguicallbackreason callbackReason,
                          void* userData, void* callData)
{
   animation_player_view& view = ToAnimationPlayerView (userData);
   animation_player& player = view.GetAnimationPlayer ();

   if (callbackReason == MGCB_REFRESH)
   {
      const string margin (" ");
      const string& path = player.GetAnimationPath ();
      if (path.empty ())
         mgTextSetString (gui, (margin + "Load an animation file").c_str ());
      else
      {
         size_t index = path.find_last_of ("/\\");
         if (index != string::npos)
         {
            const string& filename = path.substr (index + 1);
            if (!filename.empty ())
               mgTextSetString (gui, (margin + filename).c_str ());
         }
      }
   }

   return MSTAT_OK;
}

void InitializeGuiCallbacks (animation_player_view* pView)
{
   assert(pView);

   mggui dialog = pView->GetDialog ();
   mggui gui = NULL;

   if (gui = mgFindGuiById (dialog, IDC_STOP))
   {
      mgSetGuiCallback (gui, MGCB_ACTIVATE | MGCB_REFRESH, StopCallback, pView);
      mgGuiSetToolTip (gui, "Stop Animation");
   }

   if (gui = mgFindGuiById (dialog, IDC_PREVIOUS))
   {
      mgSetGuiCallback (gui, MGCB_ACTIVATE | MGCB_REFRESH, PreviousCallback, pView);
      mgGuiSetToolTip (gui, "Go to Previous Frame");
   }

   if (gui = mgFindGuiById (dialog, IDC_PLAY))
   {
      mgSetGuiCallback (gui, MGCB_ACTIVATE | MGCB_REFRESH, PlayCallback, pView);
      mgGuiSetToolTip (gui, "Play Animation");
   }

   if (gui = mgFindGuiById (dialog, IDC_NEXT))
   {
      mgSetGuiCallback (gui, MGCB_ACTIVATE | MGCB_REFRESH, NextCallback, pView);
      mgGuiSetToolTip (gui, "Go to Next Frame");
   }

   if (gui = mgFindGuiById (dialog, IDC_LOAD))
   {
      mgSetGuiCallback (gui, MGCB_ACTIVATE | MGCB_REFRESH, LoadCallback, pView);
      mgGuiSetToolTip (gui, "Load Animation");
      mgpixmap pixmap = mgResourceGetPixmap (NULL, MPIXMAP_OPENFILE);
      mgSetPixmap (gui, pixmap);
   }

   if (gui = mgFindGuiById (dialog, IDC_SLIDER))
      mgSetGuiCallback (gui, MGCB_ACTIVATE | MGCB_REFRESH, SliderCallback, pView);

   if (gui = mgFindGuiById (dialog, IDC_FRAME_TEXT_BOX))
      mgSetGuiCallback (gui, MGCB_ACTIVATE | MGCB_REFRESH, FrameTextBoxCallback, pView);

   if (gui = mgFindGuiById (dialog, IDC_FRAME_COUNT))
      mgSetGuiCallback (gui, MGCB_REFRESH, FrameCountCallback, pView);

   if (gui = mgFindGuiById (dialog, IDC_MESSAGE))
      mgSetGuiCallback (gui, MGCB_REFRESH, MessageCallback, pView);
}

mgstatus DialogProc (mggui dialog, mgdialogid dialogId, mgguicallbackreason callbackReason,
   void* userData, void* callData)
{
   animation_player_view* pView = static_cast<animation_player_view*>(userData);
   assert(pView);

   animation_player& player = pView->GetAnimationPlayer ();

   switch (callbackReason)
   {
   case MGCB_INIT:
      {
         pView->InitializeDialog (dialog);
         refresh_dialog_functor* pFunctor = new refresh_dialog_functor (dialog);
         shared_ptr<refresh_dialog_functor> spFunctor (pFunctor);
         player.SetPostUpdateFrameFunctor (spFunctor);
         InitializeGuiCallbacks (pView);
      }
      break;
   case MGCB_SHOW:
      break;
   case MGCB_HIDE:
      delete pView;
      pView = NULL;
      break;
   case MGCB_DESTROY:
      break;
   }

   return MSTAT_OK;
}

mggui CreateAnimationPlayerDialog (mgplugintool pluginTool, void* toolData)
{
   mggui dialog = NULL;
   animation_player_view* pView = static_cast<animation_player_view*>(toolData);
   if (pView)
   {
      dialog = mgResourceGetDialog (NULL, pView->GetResource (), IDD_MAIN_DIALOG,
         MGCB_INIT | MGCB_SHOW | MGCB_HIDE | MGCB_DESTROY, DialogProc, pView);
   }

   return dialog;
}
} // end anonymous namespace

void animation_player_view::RegisterCreateDialog (mgplugintool pluginTool) const
{
   mgEditorSetCreateDialogFunc (pluginTool, CreateAnimationPlayerDialog);
}